package com.atguigu.gulimall.product.controller;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.impl.CategoryServiceImpl;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/index")
public class IndeController {
    @Resource
    private CategoryServiceImpl categoryService;
    @Autowired
    RedisTemplate<String, String> redisTemplate;
    @Autowired
    RedissonClient redisson;

    /**
     * 1.缓存穿透：防止有人恶意去并发的访问一个不存在的数据。这样会导致数据库压力过大
     * 解决方法:只要是在缓存中没取到、并且数据库中也没取到的key,就将key-value设置成key-null的形式。
     * 2.缓存雪崩：比如1w条数据在清晨的时候缓存集体失效了，这时候如果有100w的并发量,这100w并发量都会去请求数据。数据库就会爆炸。
     * 解决方法；设置数据缓存随机过期时间,这样就尽可能的避免集体失效的情况。
     * 3.缓存击穿:当100w的并发量都去访问同一个没有缓存进去的数据。都没读到缓存的情况下就会同时去数据库去取数据，数据库爆炸。
     * 解决方法:单例模式下、只需要将去缓存这一块加单例锁， 第一个线程将数据放进缓存后释放完锁后、另一个线程进来判断数据是否进缓存呢。
     * 分布式环境下：用单例锁就会有瑕疵，比如8个分布式服务器都有一把锁，就是8锁，这样只能保证每台服务器的进来的第一个线程
     * 放缓存，这样就请求了8次数据库
     * 分布式环境解决方法:使用redis分布锁。
     */
    @GetMapping("/cache")
    public List<CategoryEntity> getRedisCache() {
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
        String s = valueOperations.get("CategoryEntitys");
        if (StringUtils.isEmpty(s)) {
            return categoryService.getCatalogJsonDbWithRedisLock();
        }
        return JSON.parseObject(s, new TypeReference<List<CategoryEntity>>() {
        });
    }

    /**
     * redisson框架的优点
     * 1.看门狗机制、如果在一个线程执行业务的时候宕机了，看门狗会实时检测，宕机了就不会再给它续杯。这样就能避免死锁的情况
     * 2.不用自己写自旋，redisson底层自己实现了取不到锁会自旋，
     * 3.问题: 如果手动设置锁过期时间，如果过期时间小于执行业务时间，等到锁刚过期的时候另一个线程就会进来占锁
     */
    @GetMapping("/redisson")
    public void getRedisson() {
        //取锁
        RLock lock = redisson.getLock("lock");
        //如果拿不到锁，就会在这边等待直到锁被释放。
        //占锁
        lock.lock(10, TimeUnit.SECONDS);
        try {
            System.out.println("执行20s业务," + Thread.currentThread().getId());
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //解锁
            System.out.println("解锁");
            lock.unlock();
        }
    }

    /**
     * 读写锁
     * 1.写读:写是排他锁，写的时候，读的操作会阻塞，直到写释放锁，读才能进行
     * 2.写写:只能执行一个写的操作,另一个写得等待
     * 3.读读读读写:所有的读的业务完了才能写
     * 4.读读读读：同步操作
     *
     * @return
     */
    @GetMapping("/write")
    public String writeLock() {
        String s = "";
        //获取读写写锁
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        RLock rLock = lock.writeLock();
        System.out.println("---加写锁---");
        try {
            rLock.lock();
            System.out.println("---写数据---");
            s = UUID.randomUUID().toString();
            redisTemplate.opsForValue().set("writeLock", s);
            System.out.println("执行业务10s");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("---解写锁---");
            rLock.unlock();
        }
        return s;
    }

    @GetMapping("/read")
    public String readLock() {
        String s = "";
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        RLock rLock = lock.readLock();
        rLock.lock();
        System.out.println("---加读锁---");
        try {
            System.out.println("---读数据---");
            s = redisTemplate.opsForValue().get("writeLock");
            Thread.sleep(10000);
            System.out.println("执行读业务10s");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("---解读锁---");
            rLock.unlock();
        }
        return s;
    }

    /**
     * Semaphore
     * 信息量
     */
    @GetMapping("/park")
    public void park() {
        RSemaphore park = redisson.getSemaphore("park");
        try {
            park.acquire(1);
            System.out.println("停车");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @GetMapping("/out")
    public void out() {
        RSemaphore park = redisson.getSemaphore("park");
        park.release();
        System.out.println("溜了");
    }

    /**
     * CountDownLatch
     * 闭锁
     */
    @GetMapping("/door")
    public void door() {
        RCountDownLatch door = redisson.getCountDownLatch("door");
        try {
            door.trySetCount(5);
            door.await();
            System.out.println("关门");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @GetMapping("/go/{id}")
    public String go(@PathVariable("id") Long id) {
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.countDown();
        return id + "班走了";
    }

    /**
     * springCache整合
     * 1.引入spring-boot-starter-cache和redis依赖
     * 2.开启自动配置
     * 3.选择缓存类型为redis
     * 4.扩展：针对读多写少的业务，使用cache框架没问题
     * 但是如果针对写多的业务，需要额外加读写锁
     *
     * @return
     */
    @GetMapping("/spring/cache")
    public List<CategoryEntity> getRedisCaches() {
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("wr-lock");
        //获取读锁
        RLock rLock = readWriteLock.readLock();
        //加锁
        rLock.lock();
        //直接去请求数据库
        List<CategoryEntity> categoryEntities = categoryService.getCategoryEntities();
        rLock.unlock();
        return categoryEntities;
    }

    @GetMapping("/spring/updateCache")
    public void updateDetail() {
        RReadWriteLock readWriteLock = redisson.getReadWriteLock("wr-lock");
        RLock rLock = readWriteLock.writeLock();
        rLock.lock();
        //直接去请求数据库
        categoryService.updateDetail(new CategoryEntity());
        rLock.unlock();
    }
}
